class YOLOv8CSPDarknet(BaseBackbone):
    print("this YOLOv9 is form log39")
    arch_settings = {
        # [in_channels, out_channels, num_blocks, use_elan, use_spp]
        'P5': [
            [64, 128, 1, True, False],
            [128, 256, 2, True, False],
            [256, 512, 3, True, False],
            [512, 1024, 1, False, True]  # Fixed last channel to 1024
        ],
    }

    def __init__(self,
                 arch: str = 'P5',
                 last_stage_out_channels: int = 1024,
                 plugins: Union[dict, List[dict]] = None,
                 deepen_factor: float = 1.0,
                 widen_factor: float = 1.0,
                 input_channels: int = 3,
                 out_indices: Tuple[int] = (2, 3, 4),
                 frozen_stages: int = -1,
                 norm_cfg: ConfigType = dict(
                     type='BN', momentum=0.03, eps=0.001),
                 act_cfg: ConfigType = dict(type='SiLU', inplace=True),
                 norm_eval: bool = False,
                 init_cfg: OptMultiConfig = None):
        self.arch_settings[arch][-1][1] = last_stage_out_channels
        super().__init__(
            self.arch_settings[arch],
            deepen_factor,
            widen_factor,
            input_channels=input_channels,
            out_indices=out_indices,
            plugins=plugins,
            frozen_stages=frozen_stages,
            norm_cfg=norm_cfg,
            act_cfg=act_cfg,
            norm_eval=norm_eval,
            init_cfg=init_cfg)

        self.arch_settings[arch][-1][1] = last_stage_out_channels

        super().__init__(
            self.arch_settings[arch],
            deepen_factor,
            widen_factor,
            input_channels=input_channels,
            out_indices=out_indices,
            plugins=plugins,
            frozen_stages=frozen_stages,
            norm_cfg=norm_cfg,
            act_cfg=act_cfg,
            norm_eval=norm_eval,
            init_cfg=init_cfg)

    def build_stem_layer(self) -> nn.Module:
        """Build stem layer with proper channel scaling."""
        stem_channel = self.arch_setting[0][0]
        return ConvModule(
            self.input_channels,
            make_divisible(stem_channel, self.widen_factor),
            kernel_size=3,
            stride=2,
            padding=1,
            norm_cfg=self.norm_cfg,
            act_cfg=self.act_cfg)

    def build_stage_layer(self, stage_idx: int, setting: list) -> list:
        """Build a stage layer.

        Args:
            stage_idx (int): The index of a stage layer.
            setting (list): The architecture setting of a stage layer.
        """
        in_channels, out_channels, num_blocks, use_elan, use_spp = setting

        in_channels = make_divisible(in_channels, self.widen_factor)
        out_channels = make_divisible(out_channels, self.widen_factor)
        num_blocks = make_round(num_blocks, self.deepen_factor)

        stage = []
        stage.append(ADown(in_channels, out_channels))  # YOLOv9's anti-aliasing down

        # Main blocks
        for _ in range(num_blocks):
            if use_elan:
                # ELAN1: c1, c2, c3, c4
                stage.append(ELAN1(
                    out_channels,
                    out_channels,
                    make_divisible(out_channels//2, self.widen_factor),
                    make_divisible(out_channels//4, self.widen_factor)
                ))
            else:
                # RepNCSPELAN4: c1, c2, c3, c4, c5=1
                stage.append(RepNCSPELAN4(
                    out_channels,
                    out_channels,
                    make_divisible(out_channels//2, self.widen_factor),
                    make_divisible(out_channels//4, self.widen_factor),
                    c5=1  # Original default value
                ))

        # Replace SPPELAN with SPPELANBottleneck
        if use_spp:
            stage.append(SPPELANBottleneck(
                in_channels=out_channels,
                out_channels=out_channels,
                kernel_sizes=(5, 5, 5),  # Maintain original 3x5 pooling
                mid_channels_scale=0.5,  # Match original channel scaling
                use_conv_first=True,    # Keep initial conv like original
                norm_cfg=self.norm_cfg,  # Inherit backbone normalization
                act_cfg=self.act_cfg     # Inherit backbone activation
            ))

        return stage

    def init_weights(self):
        """Initialize the parameters."""
        if self.init_cfg is None:
            for m in self.modules():
                if isinstance(m, torch.nn.Conv2d):
                    # In order to be consistent with the source code,
                    # reset the Conv2d initialization parameters
                    m.reset_parameters()
        else:
            super().init_weights()


            04/26 07:22:19 - mmengine - INFO - bbox_mAP_copypaste: 0.349 0.503 0.379 0.184 0.381 0.477
04/26 07:22:19 - mmengine - INFO - Epoch(val) [40][5000/5000]    
coco/bbox_mAP: 0.3490  coco/bbox_mAP_50: 0.5030  coco/bbox_mAP_75: 0.3790  coco/bbox_mAP_s: 0.1840  coco/bbox_mAP_m: 0.3810  
coco/bbox_mAP_l: 0.4770  data_time: 0.0002  time: 0.0219

    print("this YOLOv9 is form log4")
    arch_settings = {
        'P5': [[64, 128, 3, False, False], [128, 256, 6, False, False],
               [256, 512, 6, True, False], [512, 0, 3, True, True]],
    }

    def __init__(self,
                 arch: str = 'P5',
                 last_stage_out_channels: int = 1024,
                 plugins: Union[dict, List[dict]] = None,
                 deepen_factor: float = 1.0,
                 widen_factor: float = 1.0,
                 input_channels: int = 3,
                 out_indices: Tuple[int] = (2, 3, 4),
                 frozen_stages: int = -1,
                 norm_cfg: ConfigType = dict(
                     type='BN', momentum=0.03, eps=0.001),
                 act_cfg: ConfigType = dict(type='SiLU', inplace=True),
                 norm_eval: bool = False,
                 init_cfg: OptMultiConfig = None):
        self.arch_settings[arch][-1][1] = last_stage_out_channels
        super().__init__(
            self.arch_settings[arch],
            deepen_factor,
            widen_factor,
            input_channels=input_channels,
            out_indices=out_indices,
            plugins=plugins,
            frozen_stages=frozen_stages,
            norm_cfg=norm_cfg,
            act_cfg=act_cfg,
            norm_eval=norm_eval,
            init_cfg=init_cfg)

    def build_stem_layer(self) -> nn.Module:
        """Build a stem layer."""
        return ConvModule(
            self.input_channels,
            make_divisible(self.arch_setting[0][0], self.widen_factor),
            kernel_size=3,
            stride=2,
            padding=1,
            norm_cfg=self.norm_cfg,
            act_cfg=self.act_cfg)

    def build_stage_layer(self, stage_idx: int, setting: list) -> list:
        """Build a stage layer.

        Args:
            stage_idx (int): The index of a stage layer.
            setting (list): The architecture setting of a stage layer.
        """
        in_channels, out_channels, num_blocks, use_adown , use_spp = setting

        in_channels = make_divisible(in_channels, self.widen_factor)
        out_channels = make_divisible(out_channels, self.widen_factor)
        num_blocks = make_round(num_blocks, self.deepen_factor)
        expand_ratio = float(0.5)

        mid_channels = int(out_channels * expand_ratio)       # c3
        rep_channels = int(mid_channels * expand_ratio)          # c4

        stage = []
        if use_adown:
            sc_down_layer = ADown(in_channels, 
                out_channels)
            stage.append(sc_down_layer)
        else:
            conv_layer = ConvModule(
                in_channels,
                out_channels,
                kernel_size=3,
                stride=2,
                padding=1,
                norm_cfg=self.norm_cfg,
                act_cfg=self.act_cfg)
            stage.append(conv_layer)

        rep_layer = RepNCSPELAN4(
                out_channels,
                out_channels,
                mid_channels,
                rep_channels
            )
        stage.append(rep_layer)

        if use_spp:
            spp = SPPELAN(
                out_channels,
                out_channels,
                768 // 2 
            )
            stage.append(spp)
        return stage

coco/bbox_mAP: 0.3810


#Log7
    arch_settings = {
        'P5': [[64, 128, 3, True, False], [128, 256, 6, True, False],
               [256, 512, 6, True, False], [512, 0, 3, True, True]],
    }

    def __init__(self,
                 arch: str = 'P5',
                 last_stage_out_channels: int = 1024,
                 plugins: Union[dict, List[dict]] = None,
                 deepen_factor: float = 1.5, # 1.0
                 widen_factor: float = 1.25, # 1.0
                 input_channels: int = 3,
                 out_indices: Tuple[int] = (2, 3, 4),
                 frozen_stages: int = -1,
                 norm_cfg: ConfigType = dict(
                     type='BN', momentum=0.03, eps=0.001),
                 act_cfg: ConfigType = dict(type='SiLU', inplace=True),
                 norm_eval: bool = False,
                 init_cfg: OptMultiConfig = None):
        self.arch_settings[arch][-1][1] = last_stage_out_channels
        super().__init__(
            self.arch_settings[arch],
            deepen_factor,
            widen_factor,
            input_channels=input_channels,
            out_indices=out_indices,
            plugins=plugins,
            frozen_stages=frozen_stages,
            norm_cfg=norm_cfg,
            act_cfg=act_cfg,
            norm_eval=norm_eval,
            init_cfg=init_cfg)

    def build_stem_layer(self) -> nn.Module:
        """Build a stem layer."""
        return ConvModule(
            self.input_channels,
            make_divisible(self.arch_setting[0][0], self.widen_factor),
            kernel_size=6, 
            stride=2,
            padding=2,
            norm_cfg=self.norm_cfg,
            act_cfg=self.act_cfg)

    def build_stage_layer(self, stage_idx: int, setting: list) -> list:
        """Build a stage layer.

        Args:
            stage_idx (int): The index of a stage layer.
            setting (list): The architecture setting of a stage layer.
        """
        in_channels, out_channels, num_blocks, use_adown , use_spp = setting

        in_channels = make_divisible(in_channels, self.widen_factor)
        out_channels = make_divisible(out_channels, self.widen_factor)
        num_blocks = make_round(num_blocks, self.deepen_factor)
        expand_ratio = float(0.75) # 0.5

        mid_channels = int(out_channels * expand_ratio)       # c3
        rep_channels = int(mid_channels * expand_ratio)          # c4

        stage = []
        if use_adown:
            sc_down_layer = ADown(in_channels, 
                out_channels)
            stage.append(sc_down_layer)
        else:
            conv_layer = ConvModule(
                in_channels,
                out_channels,
                kernel_size=3, 
                stride=2,
                padding=1,
                norm_cfg=self.norm_cfg,
                act_cfg=self.act_cfg)
            stage.append(conv_layer)

        rep_layer = RepNCSPELAN4(
                out_channels,
                out_channels,
                mid_channels,
                rep_channels
            )
        stage.append(rep_layer)

        if use_spp:
            spp = SPPELAN(
                out_channels,
                out_channels,
                mid_channels
            )
            stage.append(spp)
        return stage

    def init_weights(self):
        """Initialize the parameters."""
        if self.init_cfg is None:
            for m in self.modules():
                if isinstance(m, torch.nn.Conv2d):
                    # In order to be consistent with the source code,
                    # reset the Conv2d initialization parameters
                    m.reset_parameters()
        else:
            super().init_weights()

    #Log10AdamF
    arch_settings = {
        'P5': [[64, 128, 3, True, False], [128, 256, 6, True, False],
               [256, 512, 6, True, False], [512, 0, 3, True, True]],
    }

    def __init__(self,
                 arch: str = 'P5',
                 last_stage_out_channels: int = 1024,
                 plugins: Union[dict, List[dict]] = None,
                 deepen_factor: float = 1.5, # 1.0
                 widen_factor: float = 1.25, # 1.0
                 input_channels: int = 3,
                 out_indices: Tuple[int] = (2, 3, 4),
                 frozen_stages: int = -1,
                 norm_cfg: ConfigType = dict(
                     type='BN', momentum=0.03, eps=0.001),
                 act_cfg: ConfigType = dict(type='SiLU', inplace=True),
                 norm_eval: bool = False,
                 init_cfg: OptMultiConfig = None):
        self.arch_settings[arch][-1][1] = last_stage_out_channels
        super().__init__(
            self.arch_settings[arch],
            deepen_factor,
            widen_factor,
            input_channels=input_channels,
            out_indices=out_indices,
            plugins=plugins,
            frozen_stages=frozen_stages,
            norm_cfg=norm_cfg,
            act_cfg=act_cfg,
            norm_eval=norm_eval,
            init_cfg=init_cfg)

    def build_stem_layer(self) -> nn.Module:
        """Build a stem layer."""
        return ConvModule(
            self.input_channels,
            make_divisible(self.arch_setting[0][0], self.widen_factor),
            kernel_size=6, 
            stride=2,
            padding=2,
            norm_cfg=self.norm_cfg,
            act_cfg=self.act_cfg)

    def build_stage_layer(self, stage_idx: int, setting: list) -> list:
        """Build a stage layer.

        Args:
            stage_idx (int): The index of a stage layer.
            setting (list): The architecture setting of a stage layer.
        """
        in_channels, out_channels, num_blocks, use_adown , use_spp = setting

        in_channels = make_divisible(in_channels, self.widen_factor)
        out_channels = make_divisible(out_channels, self.widen_factor)
        num_blocks = make_round(num_blocks, self.deepen_factor)
        expand_ratio = float(0.75) # 0.5

        mid_channels = int(out_channels * expand_ratio)       # c3
        rep_channels = int(mid_channels * expand_ratio)          # c4

        stage = []
        if use_adown:
            sc_down_layer = ADown(in_channels, 
                out_channels)
            stage.append(sc_down_layer)
        else:
            conv_layer = ConvModule(
                in_channels,
                out_channels,
                kernel_size=3, 
                stride=2,
                padding=1,
                norm_cfg=self.norm_cfg,
                act_cfg=self.act_cfg)
            stage.append(conv_layer)

        rep_layer = RepNCSPELAN4(
                out_channels,
                out_channels,
                mid_channels,
                rep_channels
            )
        stage.append(rep_layer)

        if use_spp:
            spp = SPPELAN(
                out_channels,
                out_channels,
                mid_channels
            )
            stage.append(spp)
        return stage

    def init_weights(self):
        """Initialize the parameters."""
        if self.init_cfg is None:
            for m in self.modules():
                if isinstance(m, torch.nn.Conv2d):
                    # In order to be consistent with the source code,
                    # reset the Conv2d initialization parameters
                    m.reset_parameters()
        else:
            super().init_weights()

             bbox_mAP_copypaste: 0.460 0.621 0.500 0.279 0.510 0.601


class YOLOv9(BaseBackbone):
    print(" THIS IS IN THE BASE YOLOV8 REPLACE SPP")
    # From left to right:
    # in_channels, out_channels, num_blocks, add_identity, use_spp
    # the final out_channels will be set according to the param.
    arch_settings = {
        'P5': [[64, 128, 3, True, False], [128, 256, 6, True, False],
               [256, 512, 6, True, False], [512, None, 3, True, True]],
    }

    def __init__(self,
                 arch: str = 'P5',
                 last_stage_out_channels: int = 1024,
                 plugins: Union[dict, List[dict]] = None,
                 deepen_factor: float = 1.0,
                 widen_factor: float = 1.0,
                 input_channels: int = 3,
                 out_indices: Tuple[int] = (2, 3, 4),
                 frozen_stages: int = -1,
                 norm_cfg: ConfigType = dict(
                     type='BN', momentum=0.03, eps=0.001),
                 act_cfg: ConfigType = dict(type='SiLU', inplace=True),
                 norm_eval: bool = False,
                 init_cfg: OptMultiConfig = None):
        self.arch_settings[arch][-1][1] = last_stage_out_channels
        super().__init__(
            self.arch_settings[arch],
            deepen_factor,
            widen_factor,
            input_channels=input_channels,
            out_indices=out_indices,
            plugins=plugins,
            frozen_stages=frozen_stages,
            norm_cfg=norm_cfg,
            act_cfg=act_cfg,
            norm_eval=norm_eval,
            init_cfg=init_cfg)

    def build_stem_layer(self) -> nn.Module:
        """Build a stem layer."""
        return ConvModule(
            self.input_channels,
            make_divisible(self.arch_setting[0][0], self.widen_factor),
            kernel_size=3,
            stride=2,
            padding=1,
            norm_cfg=self.norm_cfg,
            act_cfg=self.act_cfg)

    def build_stage_layer(self, stage_idx: int, setting: list) -> list:
        """Build a stage layer.

        Args:
            stage_idx (int): The index of a stage layer.
            setting (list): The architecture setting of a stage layer.
        """
        in_channels, out_channels, num_blocks, add_identity, use_spp = setting

        in_channels = make_divisible(in_channels, self.widen_factor)
        out_channels = make_divisible(out_channels, self.widen_factor)
        num_blocks = make_round(num_blocks, self.deepen_factor)
        stage = []
        conv_layer = ConvModule(
            in_channels,
            out_channels,
            kernel_size=3,
            stride=2,
            padding=1,
            norm_cfg=self.norm_cfg,
            act_cfg=self.act_cfg)
        stage.append(conv_layer)

        csp_layer = CSPLayerWithTwoConv(
            out_channels,
            out_channels,
            num_blocks=num_blocks,
            add_identity=add_identity,
            norm_cfg=self.norm_cfg,
            act_cfg=self.act_cfg)
        stage.append(csp_layer)

        if use_spp:
            sppelan_layer = SPPELAN(
                out_channels,
                out_channels,
                make_divisible(out_channels * 0.5, self.widen_factor)
            )
            stage.append(sppelan_layer)
        return stage

    class YOLOv9PAFPN(YOLOv5PAFPN):
    # This is the original 
    def __init__(self,
                 in_channels: List[int],
                 out_channels: Union[List[int], int],
                 deepen_factor: float = 1.0,
                 widen_factor: float = 1.0,
                 num_csp_blocks: int = 3,
                 freeze_all: bool = False,
                 norm_cfg: ConfigType = dict(
                     type='BN', momentum=0.03, eps=0.001),
                 act_cfg: ConfigType = dict(type='SiLU', inplace=True),
                 init_cfg: OptMultiConfig = None):
        super().__init__(
            in_channels=in_channels,
            out_channels=out_channels,
            deepen_factor=deepen_factor,
            widen_factor=widen_factor,
            num_csp_blocks=num_csp_blocks,
            freeze_all=freeze_all,
            norm_cfg=norm_cfg,
            act_cfg=act_cfg,
            init_cfg=init_cfg)

    def build_reduce_layer(self, idx: int) -> nn.Module:
        """build reduce layer.
        Args:
            idx (int): layer idx.
        Returns:
            nn.Module: The reduce layer.
        """
        return nn.Identity()

    def build_top_down_layer(self, idx: int) -> nn.Module:
        """Build ELAN-based top-down layer with SPP integration."""
        in_channels_combined = make_divisible((self.in_channels[idx - 1] + self.in_channels[idx]), 
        self.widen_factor)
        out_channels_prev =  make_divisible(self.out_channels[idx - 1], self.widen_factor)
        hidden =  make_divisible(self.out_channels * 0.5 , self.widen_factor)

        layers = [
            RepNCSPELAN4(
                in_channels_combined,
                out_channels_prev,
                hidden * 2,
                hidden,
                self.num_csp_blocks
            )
        ]
        
        return layers

    def build_bottom_up_layer(self, idx: int) -> nn.Module:
        """build bottom up layer.
        Args:
            idx (int): layer idx.
        Returns:
            nn.Module: The bottom up layer.
        """

        in_channels_combined = make_divisible((self.out_channels[idx] + self.out_channels[idx + 1]),self.widen_factor)
        out_channels_prev =  make_divisible(self.out_channels[idx + 1], self.widen_factor)
        hidden =  make_divisible(self.out_channels * 0.5 , self.widen_factor)

        layers = [
            RepNCSPELAN4(
                in_channels_combined,
                out_channels_prev,
                hidden * 2,
                hidden,
                self.num_csp_blocks
            )
        ]
        
        return layers

        bbox_mAP_copypaste: 0.507 0.680 0.557 0.345 0.560 0.658


        2025/05/18 20:38:29 - mmengine - INFO - Evaluating bbox...
2025/05/18 20:38:55 - mmengine - INFO - bbox_mAP_copypaste: 0.510 0.683 0.558 0.334 0.561 0.664
2025/05/18 20:38:55 - mmengine - INFO - Epoch(val) [40][5000/5000]    coco/bbox_mAP: 0.5100  coco/bbox_mAP_50: 0.6830  coco/bbox_mAP_75: 0.5580  coco/bbox_mAP_s: 0.3340  coco/bbox_mAP_m: 0.5610  coco/bbox_mAP_l: 0.6640  data_time: 0.0002  time: 0.0217


2025/05/26 06:33:55 - mmengine - INFO - bbox_mAP_copypaste: 0.515 0.683 0.564 0.341 0.569 0.659
2025/05/26 06:33:56 - mmengine - INFO - Epoch(val) [40][5000/5000]    coco/bbox_mAP: 0.5150  coco/bbox_mAP_50: 0.6830  coco/bbox_mAP_75: 0.5640  coco/bbox_mAP_s: 0.3410  coco/bbox_mAP_m: 0.5690  coco/bbox_mAP_l: 0.6590  data_time: 0.0002  time: 0.0258

2025/06/07 15:38:35 - mmengine - INFO - Evaluating bbox...
2025/06/07 15:39:02 - mmengine - INFO - bbox_mAP_copypaste: 0.492 0.658 0.538 0.320 0.540 0.646
2025/06/07 15:39:02 - mmengine - INFO - Epoch(val) [40][5000/5000]    coco/bbox_mAP: 0.4920  coco/bbox_mAP_50: 0.6580  coco/bbox_mAP_75: 0.5380  coco/bbox_mAP_s: 0.3200  coco/bbox_mAP_m: 0.5400  coco/bbox_mAP_l: 0.6460  data_time: 0.0002  time: 0.0197


06/08 22:34:24 - mmengine - INFO - bbox_mAP_copypaste: 0.439 0.601 0.478 0.249 0.485 0.592
06/08 22:34:24 - mmengine - INFO - Epoch(val) [40][5000/5000]    coco/bbox_mAP: 0.4390  coco/bbox_mAP_50: 0.6010  coco/bbox_mAP_75: 0.4780  coco/bbox_mAP_s: 0.2490  coco/bbox_mAP_m: 0.4850  coco/bbox_mAP_l: 0.5920  data_time: 0.0002  time: 0.0168


# train yolov9 models
python train_dual.py --workers 8 --device 0 --batch 16 --data data/coco.yaml --img 640 --cfg models/detect/yolov9-c.yaml --weights '' --name yolov9-c --hyp hyp.scratch-high.yaml --min-items 0 --epochs 500 --close-mosaic 15

# train gelan models
# python train.py --workers 8 --device 0 --batch 32 --data data/coco.yaml --img 640 --cfg models/detect/gelan-c.yaml --weights '' --name gelan-c --hyp hyp.scratch-high.yaml --min-items 0 --epochs 500 --close-mosaic 15

# Body
    arch_settings = {
        'P5': [[64, 128, 3, True, False], [128, 256, 6, True, False],
               [256, 512, 6, True, False], [512, None, 3, True, True]],
    }

    def __init__(self,
                 arch: str = 'P5',
                 last_stage_out_channels: int = 1024,
                 plugins: Union[dict, List[dict]] = None,
                 deepen_factor: float = 1.25,
                 widen_factor: float = 1.25,
                 input_channels: int = 3,
                 out_indices: Tuple[int] = (2, 3, 4),
                 frozen_stages: int = -1,
                 norm_cfg: ConfigType = dict(
                     type='BN', momentum=0.03, eps=0.001),
                 act_cfg: ConfigType = dict(type='SiLU', inplace=True),
                 norm_eval: bool = False,
                 init_cfg: OptMultiConfig = None):
        self.arch_settings[arch][-1][1] = last_stage_out_channels
        super().__init__(
            self.arch_settings[arch],
            deepen_factor,
            widen_factor,
            input_channels=input_channels,
            out_indices=out_indices,
            plugins=plugins,
            frozen_stages=frozen_stages,
            norm_cfg=norm_cfg,
            act_cfg=act_cfg,
            norm_eval=norm_eval,
            init_cfg=init_cfg)

    def build_stem_layer(self) -> nn.Module:
        """Build a stem layer."""
        return ConvModule(
            self.input_channels,
            make_divisible(self.arch_setting[0][0], self.widen_factor),
            kernel_size=3,
            stride=2,
            padding=1,
            norm_cfg=self.norm_cfg,
            act_cfg=self.act_cfg)

    def build_stage_layer(self, stage_idx: int, setting: list) -> list:
        """Build a stage layer.

        Args:
            stage_idx (int): The index of a stage layer.
            setting (list): The architecture setting of a stage layer.
        """
        in_channels, out_channels, num_blocks, add_identity, use_spp = setting

        in_channels = make_divisible(in_channels, self.widen_factor)
        out_channels = make_divisible(out_channels, self.widen_factor)
        num_blocks = make_round(num_blocks, self.deepen_factor)
        stage = []

        conv_layer = ConvModule(
            in_channels,
            out_channels,
            kernel_size=3,
            stride=2,
            padding=1,
            norm_cfg=self.norm_cfg,
            act_cfg=self.act_cfg)
        stage.append(conv_layer)

        csp_layer = CSPLayerWithTwoConv(
            out_channels,
            out_channels,
            num_blocks=num_blocks,
            add_identity=add_identity,
            norm_cfg=self.norm_cfg,
            act_cfg=self.act_cfg)
        stage.append(csp_layer)

        if use_spp:
            sppelan_layer = SPPELAN(
                out_channels,
                out_channels,
                make_divisible(out_channels * 0.5, self.widen_factor)
            )
            stage.append(sppelan_layer)
        return stage


# Neck
            def __init__(self,
                 in_channels: List[int],
                 out_channels: Union[List[int], int],
                 deepen_factor: float = 1.25,
                 widen_factor: float = 1.25,
                 num_csp_blocks: int = 3,
                 freeze_all: bool = False,
                 norm_cfg: ConfigType = dict(
                     type='BN', momentum=0.03, eps=0.001),
                 act_cfg: ConfigType = dict(type='SiLU', inplace=True),
                 init_cfg: OptMultiConfig = None):
        super().__init__(
            in_channels=in_channels,
            out_channels=out_channels,
            deepen_factor=deepen_factor,
            widen_factor=widen_factor,
            num_csp_blocks=num_csp_blocks,
            freeze_all=freeze_all,
            norm_cfg=norm_cfg,
            act_cfg=act_cfg,
            init_cfg=init_cfg)

    def build_reduce_layer(self, idx: int) -> nn.Module:
        """build reduce layer.
        Args:
            idx (int): layer idx.
        Returns:
            nn.Module: The reduce layer.
        """
        return nn.Identity()

    def build_top_down_layer(self, idx: int) -> nn.Module:
        """Build ELAN-based top-down layer with SPP integration."""
        in_channels_combined = make_divisible((self.in_channels[idx - 1] + self.in_channels[idx]), 
        self.widen_factor)
        out_channels_prev =  make_divisible(self.out_channels[idx - 1], self.widen_factor)
        hidden =  make_divisible(out_channels_prev * 0.5 , self.widen_factor)
        
        return RepNCSPELAN4(
            in_channels_combined,
            out_channels_prev,
            hidden * 2,
            hidden,
            make_round(self.num_csp_blocks, self.widen_factor)
        )

    def build_bottom_up_layer(self, idx: int) -> nn.Module:
        """build bottom up layer.
        Args:
            idx (int): layer idx.
        Returns:
            nn.Module: The bottom up layer.
        """
        in_channels_combined = make_divisible((self.out_channels[idx] + self.out_channels[idx + 1]),self.widen_factor)
        out_channels_prev =  make_divisible(self.out_channels[idx + 1], self.widen_factor)
        hidden =  make_divisible(out_channels_prev * 0.5 , self.widen_factor)

        return RepNCSPELAN4(
            in_channels_combined,
            out_channels_prev,
            hidden * 2,
            hidden,
            make_round(self.num_csp_blocks, self.widen_factor)
        )

        bbox_mAP_copypaste: 0.440 0.601 0.481 0.248 0.488 0.587

        class ADown(nn.Module):
    def __init__(self, c1, c2):  # ch_in, ch_out
        super().__init__()
        assert c2 % 2 == 0, "Output channels must be even"
        self.c = c2 // 2
        
        # Path 1: Average Pooling Branch
        self.cv1 = nn.Sequential(
            nn.AvgPool2d(2, 2),  # Downsample first
            Conv(c1, self.c, 3, 1, 1)  # Stride=1 to maintain resolution
        )
        
        # Path 2: MaxPool + Conv Branch
        self.cv2 = nn.Sequential(
            nn.MaxPool2d(2, 2),  # Match downsample rate
            Conv(c1, self.c, 3, 1, 1)  # Stride=1 to maintain resolution
        )

    def forward(self, x):
        x1 = self.cv1(x)  # Spatial shape: (H/2, W/2)
        x2 = self.cv2(x)  # Spatial shape: (H/2, W/2)
        return torch.cat([x1, x2], 1)